/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Multisample texture size tests
 *//*--------------------------------------------------------------------*/

#include "es31fShaderTextureSizeTests.hpp"
#include "gluRenderContext.hpp"
#include "gluShaderProgram.hpp"
#include "gluPixelTransfer.hpp"
#include "gluContextInfo.hpp"
#include "glwEnums.hpp"
#include "glwFunctions.hpp"
#include "tcuTestLog.hpp"
#include "tcuStringTemplate.hpp"
#include "tcuSurface.hpp"
#include "tcuRenderTarget.hpp"
#include "deStringUtil.hpp"

using namespace glw;

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

static const char* const s_positionVertexShaderSource =	"${GLSL_VERSION_DECL}\n"
														"in highp vec4 a_position;\n"
														"void main (void)\n"
														"{\n"
														"	gl_Position = a_position;\n"
														"}\n";

static std::string specializeShader(Context& context, const char* code)
{
	glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(context.getRenderContext().getType());
	std::map<std::string, std::string> specializationMap;

	specializationMap["GLSL_VERSION_DECL"] = glu::getGLSLVersionDeclaration(glslVersion);

	return tcu::StringTemplate(code).specialize(specializationMap);
}

class TextureSizeCase : public TestCase
{
public:
	enum TextureType
	{
		TEXTURE_FLOAT_2D = 0,
		TEXTURE_FLOAT_2D_ARRAY,
		TEXTURE_INT_2D,
		TEXTURE_INT_2D_ARRAY,
		TEXTURE_UINT_2D,
		TEXTURE_UINT_2D_ARRAY,

		TEXTURE_LAST
	};

							TextureSizeCase		(Context& context, const char* name, const char* desc, TextureType type, int samples);
							~TextureSizeCase	(void);

private:
	void					init				(void);
	void					deinit				(void);
	IterateResult			iterate				(void);

	std::string				genFragmentSource	(void);
	glw::GLenum				getTextureGLTarget	(void);
	glw::GLenum				getTextureGLInternalFormat (void);

	void					createTexture		(const tcu::IVec3& size);
	void					deleteTexture		(void);
	void					runShader			(tcu::Surface& dst, const tcu::IVec3& size);
	bool					verifyImage			(const tcu::Surface& dst);

	const TextureType		m_type;
	const int				m_numSamples;
	const bool				m_isArrayType;

	glw::GLuint				m_texture;
	glw::GLuint				m_vbo;
	glw::GLuint				m_vao;
	glu::ShaderProgram*		m_shader;
	std::vector<tcu::IVec3>	m_iterations;
	int						m_iteration;

	bool					m_allIterationsPassed;
	bool					m_allCasesSkipped;
};

TextureSizeCase::TextureSizeCase (Context& context, const char* name, const char* desc, TextureType type, int samples)
	: TestCase				(context, name, desc)
	, m_type				(type)
	, m_numSamples			(samples)
	, m_isArrayType			(m_type == TEXTURE_FLOAT_2D_ARRAY || m_type == TEXTURE_INT_2D_ARRAY || m_type == TEXTURE_UINT_2D_ARRAY)
	, m_texture				(0)
	, m_vbo					(0)
	, m_vao					(0)
	, m_shader				(DE_NULL)
	, m_iteration			(0)
	, m_allIterationsPassed	(true)
	, m_allCasesSkipped		(true)
{
	DE_ASSERT(type < TEXTURE_LAST);
}

TextureSizeCase::~TextureSizeCase (void)
{
	deinit();
}

void TextureSizeCase::init (void)
{
	static const tcu::IVec2 testSizes2D[] =
	{
		tcu::IVec2(1,	1),
		tcu::IVec2(1,	4),
		tcu::IVec2(4,	8),
		tcu::IVec2(21,	11),
		tcu::IVec2(107,	254),
		tcu::IVec2(-1,	3),
		tcu::IVec2(3,	-1),
	};
	static const tcu::IVec3 testSizes3D[] =
	{
		tcu::IVec3(1,	1,		1),
		tcu::IVec3(1,	4,		7),
		tcu::IVec3(4,	8,		12),
		tcu::IVec3(21,	11,		9),
		tcu::IVec3(107,	254,	2),
		tcu::IVec3(-1,	3,		3),
		tcu::IVec3(3,	-1,		3),
		tcu::IVec3(4,	4,		-1),
	};
	static const tcu::Vec4 fullscreenQuad[] =
	{
		tcu::Vec4(-1.0f,  1.0f, 0.0f, 1.0f),
		tcu::Vec4(-1.0f, -1.0f, 0.0f, 1.0f),
		tcu::Vec4( 1.0f,  1.0f, 0.0f, 1.0f),
		tcu::Vec4( 1.0f, -1.0f, 0.0f, 1.0f)
	};

	glu::RenderContext&		rc					= m_context.getRenderContext();
	const glw::Functions&	gl					= rc.getFunctions();
	const bool				supportsES32orGL45	= glu::contextSupports(rc.getType(), glu::ApiType::es(3, 2)) ||
												  glu::contextSupports(rc.getType(), glu::ApiType::core(4, 5));

	// requirements
	if (m_isArrayType && !supportsES32orGL45 && !m_context.getContextInfo().isExtensionSupported("GL_OES_texture_storage_multisample_2d_array"))
		TCU_THROW(NotSupportedError, "Test requires OES_texture_storage_multisample_2d_array extension");

	if (m_context.getRenderTarget().getWidth() < 1 || m_context.getRenderTarget().getHeight() < 1)
		TCU_THROW(NotSupportedError, "rendertarget size must be at least 1x1");

	glw::GLint				maxTextureSize		= 0;
	glw::GLint				maxTextureLayers	= 0;
	glw::GLint				maxSamples			= 0;

	gl.getIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
	gl.getIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &maxTextureLayers);
	gl.getInternalformativ(getTextureGLTarget(), getTextureGLInternalFormat(), GL_SAMPLES, 1, &maxSamples);

	if (m_numSamples > maxSamples)
		TCU_THROW(NotSupportedError, "sample count is not supported");

	// gen shade

	m_shader = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, s_positionVertexShaderSource)) << glu::FragmentSource(genFragmentSource()));
	m_testCtx.getLog() << *m_shader;
	if (!m_shader->isOk())
		throw tcu::TestError("shader build failed");

	// gen buffer

	gl.genBuffers(1, &m_vbo);
	gl.bindBuffer(GL_ARRAY_BUFFER, m_vbo);
	gl.bufferData(GL_ARRAY_BUFFER, sizeof(fullscreenQuad), fullscreenQuad, GL_STATIC_DRAW);

	if (!glu::isContextTypeES(m_context.getRenderContext().getType()))
		gl.genVertexArrays(1, &m_vao);

	// gen iterations

	m_testCtx.getLog()
		<< tcu::TestLog::Message
		<< "GL_MAX_TEXTURE_SIZE = " << maxTextureSize << "\n"
		<< "GL_MAX_ARRAY_TEXTURE_LAYERS = " << maxTextureLayers
		<< tcu::TestLog::EndMessage;

	if (!m_isArrayType)
	{
		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(testSizes2D); ++ndx)
		{
			if (testSizes2D[ndx].x() <= maxTextureSize && testSizes2D[ndx].y() <= maxTextureSize)
			{
				const int w = (testSizes2D[ndx].x() < 0) ? (maxTextureSize) : (testSizes2D[ndx].x());
				const int h = (testSizes2D[ndx].y() < 0) ? (maxTextureSize) : (testSizes2D[ndx].y());

				m_iterations.push_back(tcu::IVec3(w, h, 0));
			}
		}
	}
	else
	{
		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(testSizes3D); ++ndx)
		{
			if (testSizes3D[ndx].x() <= maxTextureSize && testSizes3D[ndx].y() <= maxTextureSize && testSizes3D[ndx].z() <= maxTextureLayers)
			{
				const int w = (testSizes3D[ndx].x() < 0) ? (maxTextureSize)		: (testSizes3D[ndx].x());
				const int h = (testSizes3D[ndx].y() < 0) ? (maxTextureSize)		: (testSizes3D[ndx].y());
				const int d = (testSizes3D[ndx].z() < 0) ? (maxTextureLayers)	: (testSizes3D[ndx].z());

				m_iterations.push_back(tcu::IVec3(w, h, d));
			}
		}
	}
}

void TextureSizeCase::deinit (void)
{
	if (m_texture)
	{
		m_context.getRenderContext().getFunctions().deleteTextures(1, &m_texture);
		m_texture = 0;
	}

	if (m_vbo)
	{
		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_vbo);
		m_vbo = 0;
	}

	if (m_vao)
	{
		m_context.getRenderContext().getFunctions().deleteVertexArrays(1, &m_vao);
		m_vao = 0;
	}

	if (m_shader)
	{
		delete m_shader;
		m_shader = DE_NULL;
	}
}

TextureSizeCase::IterateResult TextureSizeCase::iterate (void)
{
	tcu::Surface	result		(1, 1);
	bool			skipTest	= false;

	m_testCtx.getLog() << tcu::TestLog::Message << "\nIteration " << (m_iteration+1) << " / " << (int)m_iterations.size() << tcu::TestLog::EndMessage;

	try
	{
		// set texture size

		createTexture(m_iterations[m_iteration]);

		// query texture size

		runShader(result, m_iterations[m_iteration]);
	}
	catch (glu::OutOfMemoryError&)
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "Got GL_OUT_OF_MEMORY, skipping this size" << tcu::TestLog::EndMessage;

		skipTest = true;
	}

	// free resources

	deleteTexture();

	// queried value was correct?

	if (!skipTest)
	{
		m_allCasesSkipped = false;

		if (!verifyImage(result))
			m_allIterationsPassed = false;
	}

	// final result

	if (++m_iteration < (int)m_iterations.size())
		return CONTINUE;

	if (!m_allIterationsPassed)
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "One or more test sizes failed." << tcu::TestLog::EndMessage;
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid texture size");
	}
	else if (m_allCasesSkipped)
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "Could not test any texture size, texture creation failed." << tcu::TestLog::EndMessage;
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "All test texture creations failed");
	}
	else
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "All texture sizes passed." << tcu::TestLog::EndMessage;
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	}

	return STOP;
}

std::string TextureSizeCase::genFragmentSource (void)
{
	static const char* const templateSource =	"${GLSL_VERSION_DECL}\n"
												"${EXTENSION_STATEMENT}"
												"layout(location = 0) out highp vec4 fragColor;\n"
												"uniform highp ${SAMPLERTYPE} u_sampler;\n"
												"uniform highp ${SIZETYPE} u_size;\n"
												"void main (void)\n"
												"{\n"
												"	const highp vec4 okColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
												"	const highp vec4 failColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
												"	fragColor = (textureSize(u_sampler) == u_size) ? (okColor) : (failColor);\n"
												"}\n";

	std::map<std::string, std::string> args;

	switch (m_type)
	{
		case TEXTURE_FLOAT_2D:			args["SAMPLERTYPE"] = "sampler2DMS";		break;
		case TEXTURE_FLOAT_2D_ARRAY:	args["SAMPLERTYPE"] = "sampler2DMSArray";	break;
		case TEXTURE_INT_2D:			args["SAMPLERTYPE"] = "isampler2DMS";		break;
		case TEXTURE_INT_2D_ARRAY:		args["SAMPLERTYPE"] = "isampler2DMSArray";	break;
		case TEXTURE_UINT_2D:			args["SAMPLERTYPE"] = "usampler2DMS";		break;
		case TEXTURE_UINT_2D_ARRAY:		args["SAMPLERTYPE"] = "usampler2DMSArray";	break;
		default:
			DE_ASSERT(DE_FALSE);
	}

	if (!m_isArrayType)
		args["SIZETYPE"] = "ivec2";
	else
		args["SIZETYPE"] = "ivec3";

	const glu::ContextType	contextType			= m_context.getRenderContext().getType();
	const bool				supportsES32orGL45	= glu::contextSupports(contextType, glu::ApiType::es(3, 2)) ||
												  glu::contextSupports(contextType, glu::ApiType::core(4, 5));

	if (m_isArrayType && !supportsES32orGL45)
		args["EXTENSION_STATEMENT"] = "#extension GL_OES_texture_storage_multisample_2d_array : require\n";
	else
		args["EXTENSION_STATEMENT"] = "";

	args["GLSL_VERSION_DECL"] = glu::getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(contextType));

	return tcu::StringTemplate(templateSource).specialize(args);
}

glw::GLenum TextureSizeCase::getTextureGLTarget (void)
{
	switch (m_type)
	{
		case TEXTURE_FLOAT_2D:			return GL_TEXTURE_2D_MULTISAMPLE;
		case TEXTURE_FLOAT_2D_ARRAY:	return GL_TEXTURE_2D_MULTISAMPLE_ARRAY;
		case TEXTURE_INT_2D:			return GL_TEXTURE_2D_MULTISAMPLE;
		case TEXTURE_INT_2D_ARRAY:		return GL_TEXTURE_2D_MULTISAMPLE_ARRAY;
		case TEXTURE_UINT_2D:			return GL_TEXTURE_2D_MULTISAMPLE;
		case TEXTURE_UINT_2D_ARRAY:		return GL_TEXTURE_2D_MULTISAMPLE_ARRAY;
		default:
			DE_ASSERT(DE_FALSE);
			return 0;
	}
}

glw::GLenum TextureSizeCase::getTextureGLInternalFormat (void)
{
	switch (m_type)
	{
		case TEXTURE_FLOAT_2D:			return GL_RGBA8;
		case TEXTURE_FLOAT_2D_ARRAY:	return GL_RGBA8;
		case TEXTURE_INT_2D:			return GL_R8I;
		case TEXTURE_INT_2D_ARRAY:		return GL_R8I;
		case TEXTURE_UINT_2D:			return GL_R8UI;
		case TEXTURE_UINT_2D_ARRAY:		return GL_R8UI;
		default:
			DE_ASSERT(DE_FALSE);
			return 0;
	}
}

void TextureSizeCase::createTexture (const tcu::IVec3& size)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	if (!m_isArrayType)
		m_testCtx.getLog() << tcu::TestLog::Message << "Creating texture with size " << size.x() << "x" << size.y() << tcu::TestLog::EndMessage;
	else
		m_testCtx.getLog() << tcu::TestLog::Message << "Creating texture with size " << size.x() << "x" << size.y() << "x" << size.z() << tcu::TestLog::EndMessage;

	gl.genTextures(1, &m_texture);
	gl.bindTexture(getTextureGLTarget(), m_texture);
	GLU_EXPECT_NO_ERROR(gl.getError(), "texture gen");

	if (!m_isArrayType)
		gl.texStorage2DMultisample(getTextureGLTarget(), m_numSamples, getTextureGLInternalFormat(), size.x(), size.y(), GL_FALSE);
	else
		gl.texStorage3DMultisample(getTextureGLTarget(), m_numSamples, getTextureGLInternalFormat(), size.x(), size.y(), size.z(), GL_FALSE);
	GLU_EXPECT_NO_ERROR(gl.getError(), "texStorage");
}

void TextureSizeCase::deleteTexture (void)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	if (m_texture)
	{
		gl.deleteTextures(1, &m_texture);
		m_texture = 0;

		GLU_EXPECT_NO_ERROR(gl.getError(), "texture delete");
	}
}

void TextureSizeCase::runShader (tcu::Surface& dst, const tcu::IVec3& size)
{
	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
	const int				positionLoc			= gl.getAttribLocation(m_shader->getProgram(), "a_position");
	const int				shaderSamplerLoc	= gl.getUniformLocation(m_shader->getProgram(), "u_sampler");
	const int				shaderSizeLoc		= gl.getUniformLocation(m_shader->getProgram(), "u_size");

	m_testCtx.getLog() << tcu::TestLog::Message << "Running the verification shader." << tcu::TestLog::EndMessage;

	GLU_EXPECT_NO_ERROR(gl.getError(), "preclear");
	gl.viewport(0, 0, 1, 1);
	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	gl.clear(GL_COLOR_BUFFER_BIT);
	GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

	if (m_vao)
		gl.bindVertexArray(m_vao);

	gl.bindBuffer(GL_ARRAY_BUFFER, m_vbo);
	gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
	gl.enableVertexAttribArray(positionLoc);
	GLU_EXPECT_NO_ERROR(gl.getError(), "vertexAttrib");

	gl.useProgram(m_shader->getProgram());
	gl.uniform1i(shaderSamplerLoc, 0);
	if (m_isArrayType)
		gl.uniform3iv(shaderSizeLoc, 1, size.getPtr());
	else
		gl.uniform2iv(shaderSizeLoc, 1, size.getPtr());
	GLU_EXPECT_NO_ERROR(gl.getError(), "setup program");

	gl.bindTexture(getTextureGLTarget(), m_texture);
	GLU_EXPECT_NO_ERROR(gl.getError(), "bindtex");

	gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
	GLU_EXPECT_NO_ERROR(gl.getError(), "drawArrays");

	gl.disableVertexAttribArray(positionLoc);
	gl.useProgram(0);
	if (m_vao)
		gl.bindVertexArray(0);
	GLU_EXPECT_NO_ERROR(gl.getError(), "cleanup");

	gl.finish();
	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
	GLU_EXPECT_NO_ERROR(gl.getError(), "readPixels");
}

bool TextureSizeCase::verifyImage (const tcu::Surface& dst)
{
	DE_ASSERT(dst.getWidth() == 1 && dst.getHeight() == 1);

	const int		colorThresholdRed	= 1 << (8 - m_context.getRenderTarget().getPixelFormat().redBits);
	const int		colorThresholdGreen	= 1 << (8 - m_context.getRenderTarget().getPixelFormat().greenBits);
	const int		colorThresholdBlue	= 1 << (8 - m_context.getRenderTarget().getPixelFormat().blueBits);
	const tcu::RGBA	color				= dst.getPixel(0,0);

	m_testCtx.getLog() << tcu::TestLog::Message << "Verifying image." << tcu::TestLog::EndMessage;

	// green
	if (color.getRed() < colorThresholdRed && color.getGreen() > 255 - colorThresholdGreen && color.getBlue() < colorThresholdBlue)
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "Result ok." << tcu::TestLog::EndMessage;
		return true;
	}
	// red
	else if (color.getRed() > 255 - colorThresholdRed && color.getGreen() < colorThresholdGreen && color.getBlue() < colorThresholdBlue)
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "Image size incorrect." << tcu::TestLog::EndMessage;
		return false;
	}

	m_testCtx.getLog() << tcu::TestLog::Message << "Expected either green or red pixel, got " << color << tcu::TestLog::EndMessage;
	return false;
}

} // anonymous

ShaderTextureSizeTests::ShaderTextureSizeTests (Context& context)
	: TestCaseGroup(context, "texture_size", "Texture size tests")
{
}

ShaderTextureSizeTests::~ShaderTextureSizeTests (void)
{
}

void ShaderTextureSizeTests::init (void)
{
	static const struct SamplerType
	{
		TextureSizeCase::TextureType	type;
		const char*						name;
	} samplerTypes[] =
	{
		{ TextureSizeCase::TEXTURE_FLOAT_2D,		"texture_2d"			},
		{ TextureSizeCase::TEXTURE_FLOAT_2D_ARRAY,	"texture_2d_array"		},
		{ TextureSizeCase::TEXTURE_INT_2D,			"texture_int_2d"		},
		{ TextureSizeCase::TEXTURE_INT_2D_ARRAY,	"texture_int_2d_array"	},
		{ TextureSizeCase::TEXTURE_UINT_2D,			"texture_uint_2d"		},
		{ TextureSizeCase::TEXTURE_UINT_2D_ARRAY,	"texture_uint_2d_array"	},
	};

	static const int sampleCounts[] = { 1, 4 };

	for (int samplerTypeNdx = 0; samplerTypeNdx < DE_LENGTH_OF_ARRAY(samplerTypes); ++samplerTypeNdx)
	{
		for (int sampleCountNdx = 0; sampleCountNdx < DE_LENGTH_OF_ARRAY(sampleCounts); ++sampleCountNdx)
		{
			const std::string name = std::string() + "samples_" + de::toString(sampleCounts[sampleCountNdx]) + "_" + samplerTypes[samplerTypeNdx].name;
			const std::string desc = std::string() + "samples count = " + de::toString(sampleCounts[sampleCountNdx]) + ", type = " + samplerTypes[samplerTypeNdx].name;

			addChild(new TextureSizeCase(m_context, name.c_str(), desc.c_str(), samplerTypes[samplerTypeNdx].type, sampleCounts[sampleCountNdx]));
		}
	}
}

} // Functional
} // gles31
} // deqp
